package com.zzb.projectfactory.framework.autodoc;
import com.zzb.projectfactory.framework.autodoc.annotation.AutoDocField;
import com.zzb.projectfactory.framework.autodoc.annotation.AutoDocMethod;
import com.zzb.projectfactory.framework.autodoc.annotation.AutoDocParam;
import com.zzb.projectfactory.framework.autodoc.bean.ConfigBean;
import com.zzb.projectfactory.framework.autodoc.bean.DocBean;
import com.zzb.projectfactory.framework.autodoc.bean.ParamBean;
import com.zzb.projectfactory.framework.autodoc.bean.ParamField;
import com.zzb.projectfactory.framework.autodoc.document.CreateDocumentFactory;
import com.zzb.projectfactory.framework.autodoc.util.DocUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

public class DocHelper
{
    
    public final static String SOURCEPATH = new File("").getAbsolutePath();

    public  static String SERVER_PATH = "src\\main\\java\\com\\projectFactory\\api";
    
    private List<Class<?>> clazzList;
    
    private List<DocBean> docBeans;
    
    public DocHelper()
    {
        clazzList = new ArrayList<>();
        docBeans = new ArrayList<>();
    }
    
    public void createDoc(ConfigBean config) throws Exception
    {
        if(StringUtils.isNotBlank(config.getServerPath())){
            DocHelper.SERVER_PATH = config.getServerPath();
        }
        File dir = new File(SOURCEPATH + File.separator + SERVER_PATH);
        System.out.println(">>>>正在载入文件夹："+SOURCEPATH + File.separator + SERVER_PATH);
        loadServerClass(dir, config.isRpcServer());
        processServerClass(config.isRpcServer(), config.getPrefix());

        //创建文档
        CreateDocumentFactory.build("json").create(docBeans,config);
//        ITextPDFUtil.createPDF(docBeans, config);
        //使用markdown吧
    }
    
    private void loadServerClass(File dir, boolean isRpcServer) throws Exception
    {
        File[] fs = dir.listFiles();
        for(int i = 0; i < fs.length; i++)
        {
            if(fs[i].isDirectory())
            {
                loadServerClass(fs[i], isRpcServer);
            }
            else
            {
                String absolutePath = fs[i].getAbsolutePath();
                String path = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.length());
                if(path.contains(".java")) path = path.substring(0, path.indexOf(".java"));
                path = path.replace("\\", ".");
                
                if(!isRpcServer && path.indexOf("Controller") > 0)
                {
                    Class<?> clazz = Class.forName(path);
                    clazzList.add(clazz);
                }
                else if(isRpcServer && (path.indexOf("Service") > 0))
                {
                    Class<?> clazz = Class.forName(path);
                    clazzList.add(clazz);
                }
            }
        }
    }
    
    private void processServerClass(boolean isRpcServer, String prefix)
    {
        for(Class<?> serverClass : clazzList)
        {
            System.out.println(">>>process serverClass " + serverClass.getSimpleName());
            String classMappingName = "";
            if(!isRpcServer)
            {
                RequestMapping classMapping = serverClass.getAnnotation(RequestMapping.class);
                if(classMapping != null && (classMapping.value().length > 0 || classMapping.value().length > 0))
                {
                    classMappingName = classMapping.value().length > 0 ? classMapping.value()[0] : classMapping.value()[0];
                }
            }
            else
            {
                classMappingName = serverClass.getSimpleName();
            }
            
            Method[] methods = serverClass.getMethods();
            if(methods != null && methods.length > 0)
            {
                processServerMethod(methods, classMappingName, isRpcServer, prefix);
            }
        }
    }
    
    private void processServerMethod(Method[] methods, String classMappingPath, boolean isRpcServer, String prefix)
    {
        for(Method method : methods)
        {
            AutoDocMethod docMethod = method.getAnnotation(AutoDocMethod.class);
            if(docMethod != null)
            {
                System.out.println(">>>>>>>process method " + method.getName());
                
                DocBean bean = new DocBean();
                bean.setAuthor(docMethod.author().getName());
                bean.setSort(docMethod.sort());
                
                bean.setActionName(docMethod.name());
                bean.setActionDesc(docMethod.description());
                bean.setCreateTime(docMethod.createTime());
                bean.setDeprecated(docMethod.deprecated());
                bean.setModelName(docMethod.module().getName());
                bean.setModelIndex(docMethod.module().getIndex());
                bean.setModifyRecodes(docMethod.modify());
                bean.setVersion(docMethod.cver().getDes());
                
                if(!isRpcServer)
                {
                    RequestMapping methodMapping = method.getAnnotation(RequestMapping.class);
                    if(methodMapping != null && methodMapping.value().length > 0 || methodMapping.value().length > 0)
                    {
                        String methodMappingPath = methodMapping.value().length > 0 ? methodMapping.value()[0]
                                : methodMapping.value()[0];
                        
                        RequestMethod[] requestMethods = methodMapping.method();
                        
                        StringBuilder actionUrl = new StringBuilder();
                        if(StringUtils.isNotBlank(prefix))
                        {
                            actionUrl.append(prefix);
                        }
                        actionUrl.append(classMappingPath);
                        actionUrl.append(methodMappingPath);
                        bean.setActionUrl(actionUrl.toString());
                        bean.setReqMethod(Arrays.toString(requestMethods));
                    }
                }
                else
                {
                    bean.setActionUrl(classMappingPath + '.' + method.getName());
                    bean.setReqMethod("[RPC]");
                }
                
                buildReqParameters(method, bean, isRpcServer);
                buildRespParameters(method, bean);
                
                docBeans.add(bean);
            }
        }
    }
    
    private void buildReqParameters(Method method, DocBean bean, boolean isRpcServer)
    {
        
        ParamBean requestParam = null;
        // 请求参数
        Parameter[] parameters = method.getParameters();
        List<ParamField> paramFields = new ArrayList<>();
        if(parameters != null && parameters.length > 0)
        {
            
            for(Parameter parameter : parameters)
            {
                Class<?> paramType = parameter.getType();
                if(paramType.getName().startsWith("com.") || Collection.class.isAssignableFrom(paramType))
                {
                    Field[] fields = paramType.getDeclaredFields();
                    boolean flag = isRpcServer;
                    if(!flag)
                    {
                        RequestBody requestBody = parameter.getAnnotation(RequestBody.class);
                        flag = requestBody != null;
                    }
                    
                    if(flag)
                    {
                        if(requestParam == null)
                        {
                            requestParam = new ParamBean();
                            requestParam.setBeanName("");
                        }
                        if(!isRpcServer)
                        {
                            bean.setReqType("application/json");
                        }
                        Type type = null;
                        if(Collection.class.isAssignableFrom(paramType))
                        {
                            type = parameter.getParameterizedType();
                            if(type instanceof ParameterizedType)
                            {
                                type = ((ParameterizedType) type).getActualTypeArguments()[0];
                            }
                        }
                        
                        loadParamField(paramType, requestParam, type);
                    }
                    else
                    {
                        loadReqParamField(bean, paramFields, paramType, fields);
                    }
                    
                }
                else
                {
                    if(!parameter.getType().getSimpleName().contains("Servlet"))
                    {
                        ParamField paramField = new ParamField();
                        AutoDocParam docParam = parameter.getAnnotation(AutoDocParam.class);
                        if(!isRpcServer)
                        {
                            RequestParam param = parameter.getAnnotation(RequestParam.class);
                            if(param != null)
                            {
                                paramField.setName(param.value());
                                paramField.setRequired(param.required());
                            }
                            else
                            {
                                PathVariable pathVar = parameter.getAnnotation(PathVariable.class);
                                if(pathVar != null)
                                {
                                    paramField.setName("{" + pathVar.value() + "}");
                                }
                                else
                                {
                                    if(docParam != null)
                                    {
                                        paramField.setName(docParam.name());
                                    }
                                    else
                                    {
                                        paramField.setName(parameter.getName());
                                    }
                                }
                                paramField.setRequired(true);
                            }
                        }
                        
                        if(docParam != null)
                        {
                            if(isRpcServer)
                            {
                                paramField.setName(docParam.name());
                            }
                            paramField.setNote(docParam.value());
                        }
                        else
                        {
                            paramField.setNote("");
                        }
                        
                        paramField.setType(parameter.getType().getSimpleName());
                        paramFields.add(paramField);
                    }
                    
                }
                
            }
        }
        if(!isRpcServer && method.getAnnotation(RequestMapping.class).params().length > 0)
        {
            ParamField paramField = new ParamField();
            paramField.setName(Arrays.toString(method.getAnnotation(RequestMapping.class).params()));
            paramField.setType("String");
            paramField.setNote("");
            paramFields.add(paramField);
        }
        
        if(!isRpcServer && StringUtils.isBlank(bean.getReqType()))
        {
            bean.setReqType("application/x-www-form-urlencoded");
        }
        bean.setRequest(requestParam);
        bean.setParams(paramFields);
    }
    
    private void loadReqParamField(DocBean bean, List<ParamField> paramFields, Class<?> paramType, Field[] fields)
    {
        Class<?> supClass = paramType.getSuperclass();
        if(supClass != null && supClass.getName().startsWith("com."))
        {
            Field[] supField = supClass.getDeclaredFields();
            loadReqParamField(bean, paramFields, supClass, supField);
        }
        if(fields != null && fields.length > 0)
        {
            for(Field field : fields)
            {
                if(DocUtils.isFieldHaveGetMethod(paramType, field))
                {
                    ParamField paramField = new ParamField();
                    paramField.setName(field.getName());
                    paramField.setType(field.getType().getSimpleName());
                    if(MultipartFile.class.isAssignableFrom(field.getType()))
                    {
                        bean.setReqType("multipart/form-data");
                    }
                    AutoDocField adf = field.getAnnotation(AutoDocField.class);
                    if(adf != null)
                    {
                        paramField.setNote(adf.value());
                        paramField.setRequired(adf.required());
                    }
                    else
                    {
                        paramField.setNote("");
                    }
                    paramFields.add(paramField);
                }
            }
        }
    }
    
    private void buildRespParameters(Method method, DocBean bean)
    {
        // 返回参数
        Class<?> returnType = method.getReturnType();
        Type type = method.getGenericReturnType();
        ParamBean responseBean = new ParamBean();
        responseBean.setBeanName("");
        responseBean.setClassName(returnType.getSimpleName());
        loadParamField(returnType, responseBean, type);
        bean.setResponse(responseBean);
    }
    
    private void loadParamField(Class<?> rootClass, ParamBean paramBean, Type type)
    {
        List<ParamField> params = paramBean.getParams();
        List<ParamBean> subBeans = paramBean.getSubBeans();
        if(null == params)
        {
            params = new ArrayList<ParamField>();
            paramBean.setParams(params);
        }
        if(null == subBeans)
        {
            subBeans = new ArrayList<ParamBean>(3);
            paramBean.setSubBeans(subBeans);
        }
        
        Field[] rfs = rootClass.getDeclaredFields();
        if(rfs != null && rfs.length > 0)
        {
            for(Field rf : rfs)
            {
                // 需过滤不是返回的字段，如果字段又为list，再提取子字段
                if(DocUtils.isFieldHaveGetMethod(rootClass, rf))
                {
                    Type rfGenericType = rf.getGenericType();
                    // 字段是否是列表类型
                    if(Collection.class.isAssignableFrom(rf.getType()))
                    {
                        String collectionType = rf.getType().getSimpleName();// 字段list类型的要加上list<xxx>
                        if(rfGenericType != null && rfGenericType instanceof ParameterizedType)
                        {
                            ParameterizedType pt = (ParameterizedType) rfGenericType;
                            Type tempType = pt.getActualTypeArguments()[0];
                            if(tempType.getTypeName().equals("T") && type != null)
                            {
                                tempType = ((ParameterizedType) type).getActualTypeArguments()[0];
                            }
                            if(!tempType.equals("T") && tempType instanceof Class)
                            {
                                Class<?> genericClazz = (Class<?>) tempType;// 得到参数泛型的第一个参数的class
                                ParamBean subBean = loadSubBean(rootClass, type, rf, collectionType, genericClazz);
                                if(subBean != null)
                                {
                                    subBeans.add(subBean);
                                }
                                
                            }
                            else if(tempType instanceof ParameterizedType)
                            {
                                Type[] actualTypes = ((ParameterizedType) tempType).getActualTypeArguments();
                                Class<?> genericClazz = (Class<?>) actualTypes[0];// 最多嵌套一层List
                                collectionType = collectionType
                                        + "<" + ((Class<?>) ((ParameterizedType) tempType).getRawType()).getSimpleName();
                                if(genericClazz instanceof Class)
                                {
                                    ParamBean subBean = loadSubBean(rootClass, type, rf, collectionType, genericClazz);
                                    if(subBean != null)
                                    {
                                        subBeans.add(subBean);
                                    }
                                }
                            }
                        }
                    }
                    else if(rf.getType().getName().startsWith("com."))
                    {
                        Type temp = null;
                        if(rfGenericType != null && rfGenericType instanceof ParameterizedType)
                        {
                            ParameterizedType pt = (ParameterizedType) rfGenericType;
                            Type tempType = pt.getActualTypeArguments()[0];
                            if(!tempType.getTypeName().equals("T") && tempType instanceof Class)
                            {
                                temp = pt;
                            }
                            else if(tempType instanceof ParameterizedType)
                            {
                                temp = pt;
                            }
                            else
                            {
                                temp = type;
                            }
                        }
                        else
                        {
                            temp = type;
                        }
                        Class<?> genericClazz = rf.getType();
                        ParamBean subBean = loadSubBean(rootClass, temp, rf, null, genericClazz);
                        if(subBean != null)
                        {
                            subBeans.add(subBean);
                        }
                    }
                    else
                    {// 普通字段
                        boolean flag = false;
                        if(rfGenericType.getTypeName().equals("T") && type != null)
                        {
                            try {
                                Type[] actualTypes = ((ParameterizedType) type).getActualTypeArguments();
                                if(actualTypes[0] instanceof ParameterizedType)
                                {
                                    Class<?> rawClass = (Class<?>) ((ParameterizedType) actualTypes[0]).getRawType();
                                    // 字段是否是集合类型
                                    if(Collection.class.isAssignableFrom(rawClass))
                                    {
                                        String collectionType = rawClass.getSimpleName();// 字段list类型的要加上list<xxx>

                                        if(actualTypes[0] != null && actualTypes[0] instanceof ParameterizedType)
                                        {
                                            ParameterizedType pt = (ParameterizedType) actualTypes[0];
                                            Type tempType = pt.getActualTypeArguments()[0];
                                            if(tempType.getTypeName().equals("T") && type != null)
                                            {
                                                tempType = ((ParameterizedType) type).getActualTypeArguments()[0];
                                            }
                                            if(!tempType.equals("T") && tempType instanceof Class)
                                            {
                                                Class<?> genericClazz = (Class<?>) tempType;// 得到参数泛型的第一个参数的class

                                                ParamBean subBean = loadSubBean(rootClass, type, rf, collectionType, genericClazz);
                                                if(subBean != null)
                                                {
                                                    subBeans.add(subBean);
                                                }
                                            }

                                        }
                                    }
                                    else
                                    {
                                        if(actualTypes[0] != null && actualTypes[0] instanceof ParameterizedType)
                                        {
                                            ParameterizedType pt = (ParameterizedType) actualTypes[0];
                                            ParamBean subBean = loadSubBean(rootClass, pt, rf, null, rawClass);
                                            if(subBean != null)
                                            {
                                                subBeans.add(subBean);
                                            }
                                        }
                                    }
                                }
                                else if(actualTypes[0] instanceof Class)
                                {
                                    Class<?> genericClazz = (Class<?>) actualTypes[0];
                                    ParamBean subBean = loadSubBean(rootClass, type, rf, null, genericClazz);
                                    if(subBean != null)
                                    {
                                        subBeans.add(subBean);
                                    }
                                }
                            } catch (Exception e) {
                                flag = true;
                            }
                        }

                        else
                        {
                            ParamField pf = new ParamField();
                            pf.setName(rf.getName());
                            pf.setType(rf.getType().getSimpleName());
                            
                            AutoDocField adField = rf.getAnnotation(AutoDocField.class);
                            if(null != adField)
                            {
                                pf.setNote(adField.value());
                                pf.setRequired(adField.required());
                            }
                            else
                            {
                                pf.setNote("");
                            }
                            params.add(pf);
                        }
                    }
                }
            }
        }
        else
        {
            if(type instanceof Class)
            {
                paramBean.setBeanName("List");
                loadParamField((Class<?>) type, paramBean, type);
            }
        }
        
        if(StringUtils.isBlank(paramBean.getClassName()))
        {
            paramBean.setClassName(rootClass.getSimpleName());
        }
        
        Class<?> supClass = rootClass.getSuperclass();
        if(supClass != null && supClass.getName().startsWith("com."))
        {
            loadParamField(supClass, paramBean, type);
        }
    }
    
    private ParamBean loadSubBean(Class<?> rootClass, Type type, Field rf, String collectionType, Class<?> genericClazz)
    {
        if(!genericClazz.isAssignableFrom(rootClass))
        {// 加此条件防止死循环递归（子bean是自己类型）
            ParamBean subBean = new ParamBean();
            subBean.setBeanName(rf.getName());// 字段名称
            AutoDocField docField = rf.getAnnotation(AutoDocField.class);
            if(docField != null)
            {
                subBean.setNote(docField.value());
                subBean.setRequired(docField.required());
            }
            if(collectionType != null)
            {
                subBean.setClassName(collectionType + "<" + genericClazz.getSimpleName() + ">");
            }
            else
            {
                subBean.setClassName(genericClazz.getSimpleName());
            }
            loadParamField(genericClazz, subBean, type);// 递归
            return subBean;
        }
        return null;
    }
    
}
